<?php
/**
 * ini 格式文件解析.
 *
 * <note><b>注意：</b>有些保留字不能作为 ini 文件中的键名，<br/>
 * 包括：null，yes，no，true 和 false。值为 null，no 和 false 等效于 ""，<br/>
 * 值为 yes 和 true 等效于 "1"。<br/>
 * 字符 {}|&~![()" 也不能用在键名的任何地方，而且这些字符在选项值中有着特殊的意义.
 * </note>
 * true,和false因为会被转义，所以如果希望在解析出的数组中false和true能转变成boolean类型的，则可以给该值加上引号
 * <code>
 * [filters]
 * filter1.isopen='true'
 * filter1.isadd=true
 * </code>
 * 则会解析成：
 * <code>
 * array(
 * 	'filters' => array(
 * 		'isopen' => true,//boolean类型
 * 		'isadd' => '1', //string 类型
 * 	)
 * )
 * </code>
 *
 * @author Qian Su <aoxue.1988.su.qian@163.com>
 * @copyright ©2003-2103 phpwind.com
 * @license http://www.windframework.com
 *
 * @version $Id: WindIniParser.php 2973 2011-10-15 19:22:48Z yishuo $
 */
class WindIniParser
{
    /**
     * ini中分割数组的标识.
     *
     * @var string
     */
    const ARRAY_SEP = '.';

    /**
     * 解析数据.
     *
     * @param string $filename ini格式文件
     * @param bool   $build    是否解析,默认为true
     *
     * @return bool
     */
    public function parse($filename, $build = true)
    {
        if (!is_file($filename)) {
            return array();
        }
        $data = parse_ini_file($filename, true);

        return $build ? $this->buildData($data) : $data;
    }

    /**
     * 构建数据.
     *
     * @param array $data 将解析出来的数据进行值解析
     *
     * @return array 解析后的数组
     */
    private function buildData(&$data)
    {
        foreach ((array) $data as $key => $value) {
            if (is_array($value)) {
                $data[$key] = $this->formatDataArray($value);
            } else {
                $this->formatDataFromString($key, $value, $data);
            }
        }

        return $data;
    }

    /**
     * 将每行ini文件转换成数组.
     *
     * @param string $key   ini文件中的键
     * @param string $value ini文件中的值
     * @param array  $data  操作数据,默认为array()
     *
     * @return array
     */
    private function toArray($key, $value, &$data = array())
    {
        if (empty($key)) {
            return array();
        }
        if (strpos($key, self::ARRAY_SEP)) {
            $start = substr($key, 0, strpos($key, self::ARRAY_SEP));
            $end = substr($key, strpos($key, self::ARRAY_SEP) + 1);
            $data[$start] = array();
            $this->toArray($end, $value, $data[$start]);
        } else {
            $__tmp = strtolower($value);
            $data[$key] = 'false' === $__tmp ? false : ('true' === $__tmp ? true : $value);
        }

        return $data;
    }

    /**
     * 解析ini格式文件成数组.
     *
     * @param array $original 原始数组
     * @param array $data     解析后的数组
     *
     * @return array
     */
    private function formatDataArray(&$original, &$data = array())
    {
        foreach ((array) $original as $key => $value) {
            $tmp = $this->toArray($key, $value);
            foreach ($tmp as $tkey => $tValue) {
                if (is_array($tValue)) {
                    (!isset($data[$tkey])) && $data[$tkey] = array();
                    $this->formatDataArray($tValue, $data[$tkey]);
                } else {
                    $data[$tkey] = $tValue;
                }
            }
        }

        return $data;
    }

    /**
     * 从字符串中合并数组.
     *
     * @param string $key   待合并的键值
     * @param string $value 待合并的数据
     * @param array  $data  操作数组
     *
     * @return array
     */
    private function formatDataFromString($key, $value, &$data)
    {
        $tmp = $this->toArray($key, $value);
        if (false === strpos($key, self::ARRAY_SEP)) {
            return $tmp;
        }
        $start = substr($key, 0, strpos($key, self::ARRAY_SEP));
        if ((!isset($data[$start]) || !is_array($data[$start])) && isset($tmp[$start])) {
            $data[$start] = $tmp[$start];
            unset($data[$key]);

            return $data;
        }

        foreach ($data as $d_key => $d_value) {
            if (!isset($tmp[$d_key]) || !is_array($tmp[$d_key])) {
                continue;
            }
            foreach ($tmp[$d_key] as $a => $b) {
                $this->merge($a, $b, $data[$start]);
            }
        }
        unset($data[$key]);

        return $data;
    }

    /**
     * 合并格式化的数组.
     *
     * @param string $key   待合并的键值
     * @param mixed  $value 待合并的数据
     * @param array  $data  合并到的数据
     *
     * @return array
     */
    private function merge($key, $value, &$data = array())
    {
        if (!is_array($value)) {
            $data[$key] = $value;

            return $data;
        }
        $v_key = array_keys($value);
        $c_key = $v_key[0];
        if (is_array($value[$c_key])) {
            $this->merge($c_key, $value[$c_key], $data[$key]);
        } else {
            $data[$key][$c_key] = $value[$c_key];
        }

        return $data;
    }
}
